{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100.0%\n",
      "100.0%\n",
      "100.0%\n",
      "100.0%"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集样本数: 60000\n",
      "测试集样本数: 10000\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torchvision import datasets, transforms\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# 数据预处理：将图像转换为张量，并进行归一化\n",
    "transform = transforms.Compose([\n",
    "    transforms.ToTensor(),                 # 将图像转为 [0, 1] 范围的 Tensor\n",
    "    transforms.Normalize((0.5,), (0.5,))   # 标准化到 [-1, 1]\n",
    "])\n",
    "\n",
    "# 加载训练集和测试集\n",
    "train_dataset = datasets.MNIST(\n",
    "    root='D:\\\\code\\\\py_machine\\\\MNIST', \n",
    "    train=True, \n",
    "    transform=transform, \n",
    "    download=True  # 因为我们已经手动下载好了 .gz 文件\n",
    ")\n",
    "\n",
    "test_dataset = datasets.MNIST(\n",
    "    root='D:\\\\code\\\\py_machine\\\\MNIST', \n",
    "    train=False, \n",
    "    transform=transform, \n",
    "    download=True\n",
    ")\n",
    "\n",
    "# 创建 DataLoader（用于批量读取）\n",
    "batch_size = 64\n",
    "train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)\n",
    "test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)\n",
    "\n",
    "print(f\"训练集样本数: {len(train_dataset)}\")\n",
    "print(f\"测试集样本数: {len(test_dataset)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/5], Loss: 0.1475\n",
      "Epoch [2/5], Loss: 0.0455\n",
      "Epoch [3/5], Loss: 0.0309\n",
      "Epoch [4/5], Loss: 0.0229\n",
      "Epoch [5/5], Loss: 0.0177\n"
     ]
    }
   ],
   "source": [
    "# 定义 CNN 模型\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "class SimpleCNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(SimpleCNN, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 32, kernel_size=3)  # 输入通道: 1 (灰度图), 输出通道: 32\n",
    "        self.conv2 = nn.Conv2d(32, 64, kernel_size=3) # 输出通道: 64\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        self.fc1 = nn.Linear(64 * 5 * 5, 128)  # 经过两次池化后尺寸为 5x5\n",
    "        self.fc2 = nn.Linear(128, 10)           # 输出 10 类（数字 0~9）\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.pool(F.relu(self.conv1(x)))\n",
    "        x = self.pool(F.relu(self.conv2(x)))\n",
    "        x = x.view(-1, 64 * 5 * 5)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "\n",
    "# 初始化模型、损失函数和优化器\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "model = SimpleCNN().to(device)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 训练模型\n",
    "def train_model(model, train_loader, criterion, optimizer, num_epochs=5):\n",
    "    model.train()\n",
    "    for epoch in range(num_epochs):\n",
    "        running_loss = 0.0\n",
    "        for images, labels in train_loader:\n",
    "            images, labels = images.to(device), labels.to(device)\n",
    "\n",
    "            # 前向传播\n",
    "            outputs = model(images)\n",
    "            loss = criterion(outputs, labels)\n",
    "\n",
    "            # 反向传播和优化\n",
    "            optimizer.zero_grad()\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "\n",
    "            running_loss += loss.item()\n",
    "\n",
    "        print(f\"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}\")\n",
    "\n",
    "# 开始训练\n",
    "train_model(model, train_loader, criterion, optimizer, num_epochs=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/5], Loss: 0.0140\n",
      "Test Accuracy: 99.00%\n",
      "Epoch [2/5], Loss: 0.0121\n",
      "Test Accuracy: 98.95%\n",
      "Epoch [3/5], Loss: 0.0097\n",
      "Test Accuracy: 99.02%\n",
      "Epoch [4/5], Loss: 0.0084\n",
      "Test Accuracy: 99.21%\n",
      "Epoch [5/5], Loss: 0.0068\n",
      "Test Accuracy: 98.86%\n"
     ]
    }
   ],
   "source": [
    "\n",
    "def evaluate_model(model, test_loader):\n",
    "    model.eval()\n",
    "    correct = 0\n",
    "    total = 0\n",
    "    with torch.no_grad():\n",
    "        for images, labels in test_loader:\n",
    "            images, labels = images.to(device), labels.to(device)\n",
    "            outputs = model(images)\n",
    "            _, predicted = torch.max(outputs.data, 1)\n",
    "            total += labels.size(0)\n",
    "            correct += (predicted == labels).sum().item()\n",
    "\n",
    "    accuracy = 100 * correct / total\n",
    "    print(f\"Test Accuracy: {accuracy:.2f}%\")\n",
    "\n",
    "#修改训练函数，每个 epoch 结束后评估一次\n",
    "def train_model(model, train_loader, test_loader, criterion, optimizer, num_epochs=5):\n",
    "    model.train()\n",
    "    for epoch in range(num_epochs):\n",
    "        running_loss = 0.0\n",
    "        for images, labels in train_loader:\n",
    "            images, labels = images.to(device), labels.to(device)\n",
    "\n",
    "            # 前向传播\n",
    "            outputs = model(images)\n",
    "            loss = criterion(outputs, labels)\n",
    "\n",
    "            # 反向传播和优化\n",
    "            optimizer.zero_grad()\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "\n",
    "            running_loss += loss.item()\n",
    "\n",
    "        print(f\"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}\")\n",
    "        evaluate_model(model, test_loader)  # 每个 epoch 后评估准确率\n",
    "\n",
    "#调用训练函数时传入 test_loader\n",
    "train_model(model, train_loader, test_loader, criterion, optimizer, num_epochs=5)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "envsname",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
